﻿/* 2012/7/30 */
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using Cosmos.IO.FileSystemOperation;
using Cosmos.IO.FileSystemPath;
using Cosmos.Windows.Forms.FileList.DynamicColumn;

namespace Cosmos.Windows.Forms.FileList
{
    /// <summary>
    /// Represents a Windows list view control, which displays a collection of file names and their thumbnails
    /// </summary>
    partial class FileThumbnailList : UserControl
    {
        /// <summary>
        /// The items of fileThumbListView are being selected by some method, and the event 'SelectedIndexChanged' should do nothing.
        /// </summary>
        private bool _selectingFileThumbListView;
        /// <summary>
        /// Prepare to rename a file. When the theItem in fileThumbListView is renamed, event 'SelectedIndexChanged' should do nothing.
        /// </summary>
        private bool _renamingFile;
        /// <summary>
        /// Prepare to refresh a thumb. When the in listView is refreshed, event 'SelectedIndexChanged' should do nothing.
        /// </summary>
        //private bool _isRefreshThumb;
        /// <summary>
        /// Stores the file for thumbBackgroundWorker if it is busy.
        /// </summary>
        private FileInfo[] _thumbDelayFiles;

        /// <summary>
        /// The hash table which contains icon thumb images which had been drawn.
        /// </summary>
        private Hashtable _iconHashtable;

        /// <summary>
        /// Column name of list view in default status.
        /// </summary>
        private const string _fileNameColumnName = "FileName";

        private IDynamicAttributeExtractor _attributeExtractor;

        /// <summary>
        /// A value indicating whether the small thumbnail icon is enabled.
        /// </summary>
        private bool _smallThumbnailIcon;

        /// <summary>
        /// A value indicating whether the large thumbnail icon is enabled.
        /// </summary>
        private bool _largeThumbnailIcon;

        public FileThumbnailList()
        {
            InitializeComponent();

            listView.AllowColumnReorder = true;
            listView.FullRowSelect = true;
            listView.HideSelection = false;
            listView.Columns.Add(_fileNameColumnName, "文件名");

            _iconHashtable = new Hashtable();
        }

        #region FileThumbnail Methods

        private ListViewItem CreateThumbItem(FileInfo file)
        {
            ListViewItem item = new ListViewItem();
            item.Name = file.Name;
            item.Text = file.Name;

            item.ImageKey = file.FullName;
            if (_attributeExtractor != null)
                _attributeExtractor.AddSubItems(item, file.Name);

            return item;
        }

        private Bitmap RetrieveIconImageByExtension(string filePath, Size imageSize)
        {
            string fileExtension = Path.GetExtension(filePath).ToLower();
            if (fileExtension.ToLower().Equals(".exe") || fileExtension.ToLower().Equals(".lnk"))
            {
#if(!Limit)
                Icon fileIcon = null;
                try { fileIcon = WindowsFileInfo.FileJumboIcon(filePath, imageSize); }
                catch { fileIcon = null; }
                if (fileIcon == null)
                    fileIcon = Icon.ExtractAssociatedIcon(filePath);
#else
                Icon fileIcon = Icon.ExtractAssociatedIcon(filePath);
#endif

                Bitmap currentImage = fileIcon.ToBitmap();

                Bitmap shownBitmap = new Bitmap(imageSize.Width, imageSize.Height);
                Graphics graphics = Graphics.FromImage(shownBitmap);

                float scaleWidth = (float)(currentImage.Width) / (float)imageSize.Width;
                float scaleHeight = (float)(currentImage.Height) / (float)imageSize.Height;
                float scale = Math.Max(scaleWidth, scaleHeight);

                float displayWidth = currentImage.Width;
                float displayHeight = currentImage.Height;
                if (scale > 1.0)
                {
                    displayWidth = displayWidth / scale;
                    displayHeight = displayHeight / scale;
                }

                float startX = (imageSize.Width - displayWidth) / 2;
                float startY = (imageSize.Height - displayHeight) / 2;

                graphics.DrawImage(currentImage, startX, startY, displayWidth, displayHeight);
                currentImage.Dispose();

                return shownBitmap;
            }
            else
            {
                string imageKey = String.Concat(fileExtension, imageSize.ToString());

                if (_iconHashtable.ContainsKey(imageKey))
                {
                    return (Bitmap)((Bitmap)_iconHashtable[imageKey]).Clone();
                }
                else
                {
#if(!Limit)
                    Icon fileIcon = null;
                    try { fileIcon = WindowsFileInfo.FileJumboIcon(filePath, imageSize); }
                    catch { fileIcon = null; }
                    if (fileIcon == null)
                        fileIcon = Icon.ExtractAssociatedIcon(filePath);
#else
                    Icon fileIcon = Icon.ExtractAssociatedIcon(filePath);
#endif

                    Bitmap currentImage = fileIcon.ToBitmap();

                    Bitmap shownBitmap = new Bitmap(imageSize.Width, imageSize.Height);
                    Graphics graphics = Graphics.FromImage(shownBitmap);

                    float scaleWidth = (float)(currentImage.Width) / (float)imageSize.Width;
                    float scaleHeight = (float)(currentImage.Height) / (float)imageSize.Height;
                    float scale = Math.Max(scaleWidth, scaleHeight);

                    float displayWidth = currentImage.Width;
                    float displayHeight = currentImage.Height;
                    if (scale > 1.0)
                    {
                        displayWidth = displayWidth / scale;
                        displayHeight = displayHeight / scale;
                    }

                    float startX = (imageSize.Width - displayWidth) / 2;
                    float startY = (imageSize.Height - displayHeight) / 2;

                    graphics.DrawImage(currentImage, startX, startY, displayWidth, displayHeight);
                    currentImage.Dispose();

                    _iconHashtable.Add(imageKey, shownBitmap);
                    return shownBitmap;
                }
            }
        }

        private Bitmap CreateThumbImage(FileInfo file, bool useImageIcon, Size imageSize)
        {
            if (!File.Exists(file.FullName))
            {
                Debug.Fail("Fail to creating the thumbnail.", String.Concat("File does not exist: ", file.Name));
                return null;
            }

            string fileExtension = file.Extension.ToLower();

            if (useImageIcon && FilePath.IsStaticImageExtension(fileExtension))
            {
                Image currentImage;
                try
                {
                    currentImage = Image.FromFile(file.FullName);
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message + file.FullName);
                    return null;
                }


                Bitmap resultBitmap = new Bitmap(imageSize.Width, imageSize.Height);
                Graphics graphics = Graphics.FromImage(resultBitmap);

                float scaleWidth = (float)(currentImage.Width) / (float)imageSize.Width;
                float scaleHeight = (float)(currentImage.Height) / (float)imageSize.Height;
                float scale = Math.Max(scaleWidth, scaleHeight);

                float displayWidth = currentImage.Width;
                float displayHeight = currentImage.Height;
                if (scale > 1.0)
                {
                    displayWidth = displayWidth / scale;
                    displayHeight = displayHeight / scale;
                }

                float startX = (imageSize.Width - displayWidth) / 2;
                float startY = (imageSize.Height - displayHeight) / 2;

                Image.GetThumbnailImageAbort callback = new Image.GetThumbnailImageAbort(delegate() { return false; });
                Image thumbnailImage = currentImage.GetThumbnailImage((int)displayWidth, (int)displayHeight, callback, IntPtr.Zero);
                graphics.DrawImage(thumbnailImage, startX, startY, displayWidth, displayHeight);
                thumbnailImage.Dispose();
                currentImage.Dispose();

                return resultBitmap;
            }
            else
            {
                Bitmap resultBitmap = RetrieveIconImageByExtension(file.FullName, imageSize);
                return resultBitmap;
            }
        }

        private void AddToThumbImageList(BackgroundWorker callerBackgroundWorker, FileInfo[] files)
        {
            int i = 0;
            Size smallImageSize = smallThumbImageList.ImageSize;
            Size largeImageSize = largeThumbImageList.ImageSize;

            int fileIndex = 0;
            while (!callerBackgroundWorker.CancellationPending)
            {
                FileInfo currentFile;
                lock (files.SyncRoot)
                {
                    if (fileIndex < files.Length)
                        currentFile = files[fileIndex];
                    else
                        break;
                }

                Bitmap smallBitmap = CreateThumbImage(currentFile, _smallThumbnailIcon, smallImageSize);
                Bitmap largeBitmap = CreateThumbImage(currentFile, _largeThumbnailIcon, largeImageSize);

                if (smallBitmap != null && largeBitmap != null)
                    callerBackgroundWorker.ReportProgress(i, new object[] { currentFile.Name, smallBitmap, largeBitmap });

                fileIndex++;
            }
        }

        #endregion

        #region fileThumbBackgroundWorkder Event Handler Methods

        private void fileThumbNormalBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker self = sender as BackgroundWorker;

            FileInfo[] files = (FileInfo[])e.Argument;
            AddToThumbImageList(self, files);
            AddToThumbImageList(self, files);
        }

        private void fileThumbNormalBackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            object[] state = (object[])e.UserState;
            string imageKey = (string)state[0];
            Image smallImageValue = (Image)state[1];
            Image largeImageValue = (Image)state[2];

            smallThumbImageList.Images.Add(imageKey, smallImageValue);
            largeThumbImageList.Images.Add(imageKey, largeImageValue);
        }

        private void fileThumbNormalBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null)
                MessageBox.Show(e.Error.Message);

            BackgroundWorker self = sender as BackgroundWorker;

            if (_thumbDelayFiles != null && !self.IsBusy)   //Processing had been delayed.
            {
                self.RunWorkerAsync(_thumbDelayFiles);
                _thumbDelayFiles = null;
            }
        }

        #endregion

        #region Event Trigger Methods

        protected void OnSelectedChanged(IndicesEventArgs e)
        {
            if (SelectedChanged != null)
                SelectedChanged(this, e);
        }

        protected void OnIndexSelected(IndexEventArgs e)
        {
            if (IndexSelected != null)
                IndexSelected(this, e);
        }

        protected void OnIndicesSelected(IndicesEventArgs e)
        {
            if (IndicesSelected != null)
                IndicesSelected(this, e);
        }

        protected void OnPathSelected(PathEventArgs e)
        {
            if (PathSelected != null)
                PathSelected(this, e);
        }

        protected void OnPathsSelected(PathsEventArgs e)
        {
            if (PathsSelected != null)
                PathsSelected(this, e);
        }

        protected void OnPathCleared(EventArgs e)
        {
            if (PathCleared != null)
                PathCleared(this, e);
        }

        protected void OnPathAdded(PathEventArgs e)
        {
            if (PathAdded != null)
                PathAdded(this, e);
        }

        protected void OnPathsAdded(PathsEventArgs e)
        {
            if (PathsAdded != null)
                PathsAdded(this, e);
        }

        protected void OnPathInserted(PathInsertedEventArgs e)
        {
            if (PathInserted != null)
                PathInserted(this, e);
        }

        protected void OnPathRemoved(PathEventArgs e)
        {
            if (PathRemoved != null)
                PathRemoved(this, e);
        }

        protected void OnIndexRemoved(IndexEventArgs e)
        {
            if (IndexRemoved != null)
                IndexRemoved(this, e);
        }

        protected void OnRenamed(RenamedEventArgs e)
        {
            if (Renamed != null)
                Renamed(this, e);
        }

        #endregion

        #region FileList Methods

        public void SelectNone()
        {
            IsSelectingListView = true;
            listView.SelectedIndices.Clear();
            IsSelectingListView = false;

            OnSelectedChanged(new IndicesEventArgs(SelectedIndices));
        }

        public void SelectAll()
        {
            IsSelectingListView = true;
            foreach (ListViewItem anItem in listView.Items)
            {
                anItem.Selected = true;
            }
            IsSelectingListView = false;

            OnSelectedChanged(new IndicesEventArgs(SelectedIndices));
        }

        public void SelectInvert()
        {
            IsSelectingListView = true;
            foreach (ListViewItem anItem in listView.Items)
            {
                anItem.Selected = !anItem.Selected;
            }
            IsSelectingListView = false;

            OnSelectedChanged(new IndicesEventArgs(SelectedIndices));
        }

        public void Select(int index)
        {

        }

        public void Select(int[] indices)
        {

        }

        public void Select(string path)
        {

        }

        public void Select(string[] paths)
        {

        }

        public bool Contains(string path)
        {
            return false;
        }

        public void Clear()
        {
            if (fileThumbNormalBackgroundWorker.IsBusy)
                fileThumbNormalBackgroundWorker.CancelAsync();

            listView.Items.Clear();
            largeThumbImageList.Images.Clear();
            smallThumbImageList.Images.Clear();
        }

        public void Add(string path)
        {
            FileInfo currentFile = new FileInfo(path);
            listView.Items.Add(CreateThumbItem(currentFile));

            Bitmap smallThumb = CreateThumbImage(currentFile, SmallThumbIcon, smallThumbImageList.ImageSize);
            Bitmap largeThumb = CreateThumbImage(currentFile, LargeThumbnailIcon, largeThumbImageList.ImageSize);
            if (smallThumb != null && largeThumb != null)
            {
                smallThumbImageList.Images.Add(path, smallThumb);
                largeThumbImageList.Images.Add(path, largeThumb);
            }
        }

        public void Add(IEnumerable<string> paths)
        {
            List<FileInfo> fileList = new List<FileInfo>();
            List<string> addedPathList = null;
            if (paths is ICollection<string>)
            {
                fileList.Capacity = ((ICollection<string>)paths).Count;
                addedPathList = new List<string>(((ICollection<string>)paths).Count);
            }
            else
            {
                addedPathList = new List<string>();
            }

            foreach (string aPath in paths)
            {
                if (!Contains(aPath))
                {
                    fileList.Add(new FileInfo(aPath));
                    addedPathList.Add(aPath);
                }
            }
            FileInfo[] files = fileList.ToArray();            

            foreach (FileInfo aFile in files)
            {
                listView.Items.Add(CreateThumbItem(aFile));
            }

            if (!fileThumbNormalBackgroundWorker.IsBusy)
            {
                _thumbDelayFiles = null;
                fileThumbNormalBackgroundWorker.RunWorkerAsync(files);
            }
            else
            {
                _thumbDelayFiles = files;
            }

            OnPathsAdded(new PathsEventArgs(addedPathList.ToArray()));
        }

        public void Remove(string path)
        {
            listView.Items.RemoveByKey(path);
            smallThumbImageList.Images.RemoveByKey(path);
            largeThumbImageList.Images.RemoveByKey(path);
        }

        public void Rename(string srcFilePath, string dstFilePath)
        {
            //string srcFileName = FilePath.GetFileName(srcFilePath);string dstFileName
            // = FilePath.Combine(FilePath.GetDirectoryName(srcFilePath), dstFileName);

            FileInfo currentFile = new FileInfo(dstFilePath);

            int theIndex = listView.Items.IndexOfKey(srcFilePath);
            if (theIndex == -1)
                MessageBox.Show(String.Concat("SetPath theItem error: ", srcFilePath, " to ", dstFilePath));
            listView.Items.RemoveAt(theIndex);
            listView.Items.Insert(theIndex, CreateThumbItem(currentFile));

            smallThumbImageList.Images.RemoveByKey(srcFilePath);
            largeThumbImageList.Images.RemoveByKey(srcFilePath);
            Bitmap smallThumb = CreateThumbImage(currentFile, SmallThumbIcon, smallThumbImageList.ImageSize);
            Bitmap largeThumb = CreateThumbImage(currentFile, LargeThumbnailIcon, largeThumbImageList.ImageSize);
            if (smallThumb != null && largeThumb != null)
            {
                smallThumbImageList.Images.Add(dstFilePath, smallThumb);
                largeThumbImageList.Images.Add(dstFilePath, largeThumb);
            }
        }

        #endregion

        #region FileList Properties

        public int SelectedIndex
        {
            get
            {
                if (listView.SelectedIndices.Count != 0)
                    return listView.SelectedIndices[0];
                else
                    return -1;
            }
            set
            {
                SelectNone();
                Select(value);
            }
        }

        public int[] SelectedIndices
        {
            get
            {
                int count = listView.SelectedIndices.Count;
                int[] selectedIndices = new int[count];
                for (int i = 0; i < count; i++)
                {
                    selectedIndices[i] = listView.SelectedIndices[i];
                }
                return selectedIndices;
            }
            set
            {
                SelectNone();
                Select(value);
            }
        }

        public string SelectedPath
        {
            get
            {
                if (listView.SelectedItems.Count != 0)
                    return listView.SelectedItems[0].Name;
                else
                    return null;
            }
            set
            {
                SelectNone();
                Select(value);
            }
        }

        public string[] SelectedPaths
        {
            get
            {
                int count = listView.SelectedItems.Count;
                string[] selectedItems = new string[count];
                for (int i = 0; i < count; i++)
                {
                    selectedItems[i] = listView.SelectedItems[i].Name;
                }
                return selectedItems;
            }
            set
            {
                SelectNone();
                Select(value);
            }
        }

        #endregion

        #region Thumbnail Properties

        public IDynamicAttributeExtractor AttributeExtractor
        {
            get { return _attributeExtractor; }
            set { _attributeExtractor = value; }
        }

        /// <summary>
        /// Prepare to rename a file. When the theItem in fileThumbListView is renamed, event 'SelectedIndexChanged' should do nothing.
        /// </summary>
        private bool IsRenamingFile
        {
            get { return _renamingFile; }
            set { _renamingFile = value; }
        }

        /// <summary>
        /// The items of fileThumbListView are being selected by some method, and the event 'SelectedIndexChanged' should do noting.
        /// </summary>
        private bool IsSelectingListView
        {
            get { return _selectingFileThumbListView; }
            set { _selectingFileThumbListView = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the small thumbnail icon is enabled.
        /// </summary>
        public bool SmallThumbIcon
        {
            get
            { return _smallThumbnailIcon; }
            set
            { _smallThumbnailIcon = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the large thumbnail icon is enabled.
        /// </summary>
        public bool LargeThumbnailIcon
        {
            get
            { return _largeThumbnailIcon; }
            set
            { _largeThumbnailIcon = value; }
        }

        #endregion

        #region FileList Events

        public event IndicesEventHandler SelectedChanged;

        public event IndexEventHandler IndexSelected;

        public event IndicesEventHandler IndicesSelected;

        public event PathEventHandler PathSelected;

        public event PathsEventHandler PathsSelected;

        public event EventHandler PathCleared;

        public event PathEventHandler PathAdded;

        public event PathsEventHandler PathsAdded;

        public event PathInsertedEventHandler PathInserted;

        public event PathEventHandler PathRemoved;

        public event IndexEventHandler IndexRemoved;

        public event RenamedEventHandler Renamed;

        #endregion
    }
}
